#include <string.h>
#include <fcntl.h>
#include "queue.h"

queue_t *queue_create(void)
{
    char sem_name[32];
    queue_t *queue = (queue_t *)malloc(sizeof(queue_t));

    if (queue) {
        INIT_LIST_HEAD(&queue->list);
#ifdef WIN32
        queue->mutex = CreateMutex(NULL, FALSE, NULL);
        if (queue->mutex == NULL) {
            perror("CreateMutex");
            free(queue);
            return NULL;
        }

        queue->not_empty = CreateSemaphore(NULL, 0, 1, NULL);
        if (queue->not_empty == NULL) {
            perror("CreateSemaphore");
            CloseHandle(queue->mutex);
            free(queue);
            return NULL;
        }
#else
        pthread_mutex_init(&queue->mutex, NULL);
#if 0
        sem_init(&queue->not_empty, 0, 0);
#else
        memset(sem_name, 0, 32);
        sprintf(sem_name, "not_empty_sem@%lx", (unsigned long)queue);
        if ((queue->not_empty = sem_open(sem_name, O_CREAT, 0644, 0)) == SEM_FAILED) {
            perror("sem_open");
            free(queue);
            return NULL;
        }
#endif
#endif
        queue->count = 0;
    }
    else perror("malloc");

    return queue;
}

int queue_push(queue_t *queue, void *data)
{
    struct __data *d;

    if (!queue || !data)
        return -1;

    d = (struct __data *)malloc(sizeof(struct __data));
    if (!d)
        return -2;

#ifdef WIN32
    WaitForSingleObject(queue->mutex, INFINITE);
#else
    pthread_mutex_lock(&queue->mutex);
#endif

    d->data = data;
    list_add_tail(&d->list_node, &queue->list);

    ++queue->count;

#ifdef WIN32
    ReleaseSemaphore(queue->not_empty, 1, NULL);
    ReleaseMutex(queue->mutex);
#else
    sem_post(queue->not_empty);
    pthread_mutex_unlock(&queue->mutex);
#endif

    return 0;
}

void *queue_pop(queue_t *queue)
{
    struct __data *d;
    void *data;

    if (!queue)
        return NULL;

#ifdef WIN32
    WaitForSingleObject(queue->mutex, INFINITE);

    if (list_empty(&queue->list)) {
        ReleaseMutex(queue->mutex);
        return NULL;
    }
#else
    pthread_mutex_lock(&queue->mutex);

    if (list_empty(&queue->list)) {
        pthread_mutex_unlock(&queue->mutex);
        return NULL;
    }
#endif

    d = list_entry(queue->list.next, struct __data, list_node);
    list_del(&d->list_node);
    --queue->count;

    data = d->data;

    free(d);

#ifdef WIN32
    ReleaseMutex(queue->mutex);
#else
    pthread_mutex_unlock(&queue->mutex);
#endif

    return data;
}

void *queue_pop_wait(queue_t *queue)
{
    struct __data *d;
    void *data;

    if (!queue)
        return NULL;

#ifdef WIN32
    while (list_empty(&queue->list)) {
        WaitForSingleObject(queue->not_empty, INFINITE);
    }
    WaitForSingleObject(queue->mutex, INFINITE);
#else
    while (list_empty(&queue->list)) {
        sem_wait(queue->not_empty);
    }
    pthread_mutex_lock(&queue->mutex);
#endif

    d = list_entry(queue->list.next, struct __data, list_node);
    list_del(&d->list_node);
    --queue->count;

    data = d->data;

    free(d);

#ifdef WIN32
    ReleaseMutex(queue->mutex);
#else
    pthread_mutex_unlock(&queue->mutex);
#endif

    return data;
}

int queue_is_empty(queue_t *queue)
{
    return queue ? list_empty(&queue->list): 1;
}

inline int queue_count(queue_t *queue)
{
    return queue->count;
}

void *queue_peek_head(queue_t *queue)
{
    struct __data *d;
    void *data = NULL;

    if (!queue)
        return NULL;

#ifdef WIN32
    WaitForSingleObject(queue->mutex, INFINITE);

    if (list_empty(&queue->list)) {
        ReleaseMutex(queue->mutex);
        return NULL;
    }
#else
    pthread_mutex_lock(&queue->mutex);

    if (list_empty(&queue->list)) {
        pthread_mutex_unlock(&queue->mutex);
        return NULL;
    }
#endif

    d = list_entry(queue->list.next, struct __data, list_node);
    if (d)
        data = d->data;

#ifdef WIN32
    ReleaseMutex(queue->mutex);
#else
    pthread_mutex_unlock(&queue->mutex);
#endif

    return data;
}

void *queue_peek_tail(queue_t *queue)
{
    struct __data *d;
    void *data = NULL;

    if (!queue)
        return NULL;

#ifdef WIN32
    WaitForSingleObject(queue->mutex, INFINITE);

    if (list_empty(&queue->list)) {
        ReleaseMutex(queue->mutex);
        return NULL;
    }
#else
    pthread_mutex_lock(&queue->mutex);

    if (list_empty(&queue->list)) {
        pthread_mutex_unlock(&queue->mutex);
        return NULL;
    }
#endif

    d = list_entry(queue->list.prev, struct __data, list_node);
    if (d)
        data = d->data;

#ifdef WIN32
    ReleaseMutex(queue->mutex);
#else
    pthread_mutex_unlock(&queue->mutex);
#endif

    return data;
}

void *queue_peek_nth(queue_t *queue, int nth)
{
    struct __data *d;
    struct list_head *list;
    void *data = NULL;
    int idx;

    if (!queue || nth <= 0)
        return NULL;

#ifdef WIN32
    WaitForSingleObject(queue->mutex, INFINITE);

    if (list_empty(&queue->list) || queue->count < nth) {
        ReleaseMutex(queue->mutex);
        return NULL;
    }
#else
    pthread_mutex_lock(&queue->mutex);

    if (list_empty(&queue->list) || queue->count < nth) {
        pthread_mutex_unlock(&queue->mutex);
        return NULL;
    }
#endif

    list = queue->list.next;

    for (idx = 0; idx < nth - 1; idx++)
        list = list->next;

    d = list_entry(list, struct __data, list_node);
    if (d)
        data = d->data;

#ifdef WIN32
    ReleaseMutex(queue->mutex);
#else
    pthread_mutex_unlock(&queue->mutex);
#endif

    return data;
}

int queue_index(queue_t *queue, void *data)
{
    int idx;

    for (idx = 1; idx <= queue->count; idx++) {
        if (data == queue_peek_nth(queue, idx))
            return idx;
    }

    return 0;
}

void *queue_steal_nth(queue_t *queue, int nth)
{
    struct __data *d;
    struct list_head *list;
    void *data = NULL;
    int idx;

    if (!queue || nth <= 0)
        return NULL;

#ifdef WIN32
    WaitForSingleObject(queue->mutex, INFINITE);

    if (list_empty(&queue->list) || queue->count < nth) {
        ReleaseMutex(queue->mutex);
        return NULL;
    }
#else
    pthread_mutex_lock(&queue->mutex);

    if (list_empty(&queue->list) || queue->count < nth) {
        pthread_mutex_unlock(&queue->mutex);
        return NULL;
    }
#endif

    list = queue->list.next;

    for (idx = 0; idx < nth - 1; idx++)
        list = list->next;

    d = list_entry(list, struct __data, list_node);
    if (d) {
        list_del(&d->list_node);
        --queue->count;
        data = d->data;
    }

#ifdef WIN32
    ReleaseMutex(queue->mutex);
#else
    pthread_mutex_unlock(&queue->mutex);
#endif

    return data;
}

void *queue_steal(queue_t *queue, void *data)
{
    return queue_steal_nth(queue, queue_index(queue, data));
}

void *queue_steal_wait(queue_t *queue, void *data)
{
    int idx;

    while (1) {
        idx = queue_index(queue, data);
        if (idx > 0)
            break;
#ifdef WIN32
        Sleep(1);
#else
        usleep(100);
#endif
    };

    return queue_steal_nth(queue, idx);
}

void queue_destroy(queue_t *queue)
{
    char sem_name[32];

    if (queue) {
        while (!queue_is_empty(queue))
            queue_pop(queue);
#ifdef WIN32
        CloseHandle(queue->mutex);
        CloseHandle(queue->not_empty);
#else
#if 0
        sem_destroy(&queue->not_empty);
#else
        memset(sem_name, 0, 32);
        sprintf(sem_name, "not_empty_sem@%lx", (unsigned long)queue);

        sem_close(queue->not_empty);
        sem_unlink(sem_name);
#endif
#endif
        free(queue);
    }
}
